package springboot.file.demo.util;

import lombok.extern.slf4j.Slf4j;
import net.coobird.thumbnailator.Thumbnails;
import org.apache.commons.fileupload.FileItem;
import org.apache.commons.fileupload.FileItemFactory;
import org.apache.commons.fileupload.disk.DiskFileItemFactory;
import org.springframework.web.multipart.MultipartFile;
import org.springframework.web.multipart.commons.CommonsMultipartFile;

import javax.imageio.ImageIO;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.OutputStream;

/**
 * 图片压缩工具类
 */
@Slf4j
public class FileUtils {

    /**
     * 判断文件大小,大于则返回true
     * @param len  文件长度
     * @param size 限制大小
     * @param unit 限制单位（B,K,M,G）
     * @return
     */
    public static boolean checkFileSize(Long len, int size, String unit) {
        double fileSize = 0;
        if ("B".equals(unit.toUpperCase())) {
            fileSize = (double) len;
        } else if ("K".equals(unit.toUpperCase())) {
            fileSize = (double) len / 1024;
        } else if ("M".equals(unit.toUpperCase())) {
            fileSize = (double) len / 1048576;
        } else if ("G".equals(unit.toUpperCase())) {
            fileSize = (double) len / 1073741824;
        }
        if (fileSize > size) {
            return true;
        }
        return false;
    }

    /**
     * 压缩文件
     * @param file 源文件 MultipartFile
     * @param maxWidth 限制文件最大width，不作限制传null
     * @param maxHeight 限制文件的最大height，不作限制传null
     * @param destSize 期望压缩后的图片大小，单位：KB
     * @return 压缩后的MultipartFile
     */
    public static MultipartFile compressFile(MultipartFile file, Integer maxWidth, Integer maxHeight,long destSize) {
        /*记录原MultipartFile，如果压缩异常就用原来的MultipartFile*/
        MultipartFile oldMultipartFile = file;
        long size = file.getSize();
        if (size <= destSize * 1024) {
            log.debug("该图片大小在期望范围以内，不必进行压缩(fileName:{})", file.getOriginalFilename());
            return file;
        }
        double compressRatio = getAccuracy(size / 1024);
        try {
            /*获取图片文件名(不带扩展名的文件名)*/
            String prefixName = getFileNameWithoutEx(file.getOriginalFilename());
            /*获取文件拓展名*/
            String suffixName = getExtensionName(file.getOriginalFilename());
            if (!checkFileType(suffixName)) {
                log.debug("该文件不支持压缩！");
                return oldMultipartFile;
            }
            String filePath = System.getProperty("user.dir") + "/data/tmp2";
            /*图片在项目中的地址(项目位置+图片名,带后缀名)*/
            String contextPath = filePath + "/" + prefixName + "." + suffixName;
            /*存的项目的中模版图片*/
            File tempFile = new File(contextPath);
            if (!tempFile.exists()) {
                /*生成图片文件*/
                org.apache.commons.io.FileUtils.copyInputStreamToFile(file.getInputStream(), tempFile);
            }
            BufferedImage image = ImageIO.read(file.getInputStream());
            if (maxWidth == null || maxHeight == null){
                Thumbnails.of(contextPath).outputQuality(compressRatio).scale(1f).toFile(contextPath);
            }else {
                if (image.getHeight() > maxHeight || image.getWidth() > maxWidth) {
                    Thumbnails.of(contextPath).size(maxWidth, maxHeight).outputQuality(compressRatio).toFile(contextPath);
                } else {
                    Thumbnails.of(contextPath).outputQuality(compressRatio).scale(1f).toFile(contextPath);
                }
            }
            /*获取压缩后的图片*/
            File f = new File(contextPath);
            FileItem fileItem = createFileItem(f, file.getOriginalFilename(), file.getContentType());
            file = new CommonsMultipartFile(fileItem);
            log.debug("压缩成功，压缩后的图片大小：{}KB", file.getSize() / 1024);
            boolean success2 = f.delete();
            boolean success = tempFile.delete();
            log.debug("删除临时file success：{} success2:{}", success,success2);
        } catch (IOException e) {
            log.error("压缩图片失败,把MultipartFile赋值为原来的值oldFile,exception:" + e);
            file = oldMultipartFile;
        }
        return file;
    }

    /**
     * 自动调节精度(经验数值)
     * @param size 源图片大小,单位：KB
     * @return 图片压缩质量比
     */
    private static double getAccuracy(long size) {
        double accuracy;
        if (size < 900) {
            accuracy = 0.85;
        } else if (size < 2047) {
            accuracy = 0.6;
        } else if (size < 3275) {
            accuracy = 0.44;
        } else {
            accuracy = 0.4;
        }
        return accuracy;
    }

    /**
     * 检查文件扩展名是否是图片格式：bmp，jpeg，jpg，png
     * @param suffixName 文件扩展名
     * @return true：是，false：不是
     */
    private static boolean checkFileType(String suffixName) {
        return FileTypeDetect.TYPE_PNG.equals(suffixName)
                || FileTypeDetect.TYPE_JPG.equals(suffixName)
                || FileTypeDetect.TYPE_JPEG.equals(suffixName)
                || FileTypeDetect.TYPE_BMP.equals(suffixName);
    }

    private static FileItem createFileItem(File file, String fieldName,String contentType) {
        /*DiskFileItemFactory()：构造一个配置好的该类的实例
         *第一个参数threshold(阈值)：以字节为单位.在该阈值之下的item会被存储在内存中，
         * 在该阈值之上的item会被当做文件存储
         *第二个参数data repository：将在其中创建文件的目录.用于配置在创建文件项目时，
         * 当文件项目大于临界值时使用的临时文件夹，默认采用系统默认的临时文件路径
         */
        FileItemFactory factory = new DiskFileItemFactory(16, null);
        //fieldName：表单字段的名称；第二个参数 ContentType；第三个参数isFormField；第四个：文件名
        FileItem item = factory.createItem(fieldName, contentType, true, file.getName());
        int bytesRead;
        byte[] buffer = new byte[8192];
        FileInputStream fis = null;
        OutputStream os = null;
        try {
            fis = new FileInputStream(file);
            os = item.getOutputStream();
            while ((bytesRead = fis.read(buffer, 0, 8192)) != -1) {
                //从buffer中得到数据进行写操作
                os.write(buffer, 0, bytesRead);
            }
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            try {
                if (os != null) {
                    os.close();
                }
                if (fis != null) {
                    fis.close();
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
        return item;
    }

    /**
     * 获取不带扩展名的文件名
     * @param filename 文件
     * @return 获取文件的前缀名 ，"."前面的部分
     */
    public static String getFileNameWithoutEx(String filename) {
        if ((filename != null) && (filename.length() > 0)) {
            int dot = filename.lastIndexOf('.');
            if ((dot > -1) && (dot < (filename.length()))) {
                return filename.substring(0, dot);
            }
        }
        return filename;
    }

    /**
     * 获取文件扩展名
     * @param filename 文件名
     * @return 获取文件扩展名，"."后面的部分
     */
    public static String getExtensionName(String filename) {
        if ((filename != null) && (filename.length() > 0)) {
            int dot = filename.lastIndexOf('.');
            if ((dot > -1) && (dot < (filename.length() - 1))) {
                return filename.substring(dot + 1);
            }
        }
        return filename;
    }

}
